package contegor.scire.core.modifications;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

import contegor.scire.core.Connectable;
import contegor.scire.core.Container;
import contegor.scire.core.DuplicateElementException;
import contegor.scire.core.Id;
import contegor.scire.core.Identifiable;
import contegor.scire.core.InfiniteSingletonList;
import contegor.scire.core.Link;
import contegor.scire.core.Modifiable;
import contegor.scire.core.NeedsPrecedent;
import contegor.scire.core.ReturnPoint;
import contegor.scire.core.ReturnPointAdapter;
import contegor.scire.core.ReturnPointDecoratorForErrorsHandling;
import contegor.scire.core.Task;
import contegor.scire.core.TaskId;
import contegor.scire.core.TrackExternalLinks;
import contegor.scire.core.TransactionException;
import contegor.scire.core.Version;
import contegor.scire.core.WithConsequences;
import contegor.scire.core.WithSubsequentsExtensible;
import contegor.scire.core.WrongStateException;
import contegor.scire.core.Link.Flow;

/**
 * {@link Modification} used to notify a {@link Modifiable} that it must create inside itself a {@link Link}.
 * The source and target of that link should be <code>{@link Connectable}</code>. The source is given
 * by the precedent's result. The target by its {@link Id}.
 *
 * @version $Revision$
 * @author  Héctor Cirbián Pousa ($Author$)
 */
@Version("$Id$")
public class ChainCreateLink extends Modification<Link> implements WithConsequences<Link, Void>, NeedsPrecedent<Connectable, Link>, WithSubsequentsExtensible<Link, Link> {
  private static final long serialVersionUID = -2778206559830783867L;

  private Link.Flow         flow; 
  private Id                target;
  private List<Connectable> sources;
  private List<Task<Void>>  consequences;
  private List<Link>        results;
  private List<NeedsPrecedent<? super Link, Link>> subsequents;

  public ChainCreateLink(Flow flow, Id target) {
    super(null);
    this.flow        = flow;
    this.target      = target;
    this.subsequents = null;
  }

  @Override
  public int getDepth() {
    return 1;
  }

  @Override
  public void process(Container targetContainer) throws DuplicateElementException, TransactionException, WrongStateException {
    if (this.sources.size() == 0)
      throw new WrongStateException("No sources to create links where received. Target: " + this.target + " Source container: " + this.targetContainerId);
    results = new ArrayList<Link>(this.sources.size());
    for (Connectable source: this.sources) {
      // The Modifiable checks if the source belongs to it.
      Link result = ((Modifiable)targetContainer).createLink(relatedTime, this.taskId.getTransactionId(), flow, source, target);
      this.results.add(result);
      returnPoint.manage(this.taskId, result);
    }
    if (!(this.targetContainerId.isParentOf(target) || Id.RAW_ROOT.isAncestorOf(target))) {
      this.consequences = new ArrayList<Task<Void>>(1);
      this.consequences.add(new TrackExternalLinks(this.taskId.next(true), target.getParentId(), target.getLastIdFragment(), this.targetContainerId, this.sources.size()));
    }
    else {
      this.consequences = Collections.emptyList();
    }
  }

  @Override
  public void setPrecedent(Task<? extends Connectable> precedent, Collection<? extends Connectable> results) {
    this.sources = new ArrayList<Connectable>(results);
    if (this.sources.size() > 0) {
      Connectable res = this.sources.get(0);
      while (res instanceof Link)
        res = ((Link)res).getSource();
      this.targetContainerId = ((Identifiable)res).getId().getParentId();
    }
    else
      this.targetContainerId = precedent.getTargetContainerId();
  }

  @Override
  public List<? extends Task<Void>> getConsequences() {
    return this.consequences;
  }

  @Override
  public List<? extends ReturnPoint<Void>> getConsequencesReturnPoints() {
    if (this.consequences == null)
      return Collections.emptyList();
    return new InfiniteSingletonList<ReturnPoint<Void>>(new ReturnPointAdapter<Void>() {
      public void manage(TaskId taskId, Exception exception) {
        returnPoint.manage(ChainCreateLink.this.taskId, exception);          
      }
    }, this.consequences.size());
  }

  @Override
  public List<? extends NeedsPrecedent<? super Link, Link>> getSubsequentTasks() {
    if (this.subsequents == null)
      return Collections.emptyList();
    return this.subsequents;
  }

  @Override
  public List<? extends Collection<Link>> getResultsForSubsequentTasks() {
    if (this.subsequents == null)
      return Collections.emptyList();
    return new InfiniteSingletonList<List<Link>>(results, this.subsequents.size());
  }

  @Override
  public List<? extends ReturnPoint<Link>> getSubsequentTasksReturnPoints() {
    if (this.subsequents == null)
      return Collections.emptyList();
    return new InfiniteSingletonList<ReturnPoint<Link>>(new ReturnPointDecoratorForErrorsHandling<Link>(this.returnPoint), this.subsequents.size());
  }

  @Override
  public ChainCreateLink withIt(NeedsPrecedent<? super Link, Link> subsequent) {
    if (this.subsequents == null)
      this.subsequents = new LinkedList<NeedsPrecedent<? super Link,Link>>();
    this.subsequents.add(subsequent);
    return this;
  }

  @Override
  public void setTaskId(TaskId taskId) {
    super.setTaskId(taskId);
    if (subsequents != null)
      for (NeedsPrecedent<?,?> subsequent: subsequents)
        subsequent.setTaskId(this.taskId.next(true));
  }
}
